﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using GIMS.Def.Flags;
using GIMS.Def.Operators;
using QuickGraph;
using GIMS.Def.Arguments;

namespace GIMS.GUI.GraphItems
{
    public class CustomGraph : BidirectionalGraph<CustomVertex, CustomEdge>
    {
        public delegate void RefreshNeededEventHandler(object sender, RefreshNeededEventArgs e);
        public event RefreshNeededEventHandler refreshNeededEvent;
        private List<CustomVertex> afterDeleteList;

        #region Constructors

        public CustomGraph() { }

        public CustomGraph(bool allowParallelEdges)
            : base(allowParallelEdges) { }

        public CustomGraph(bool allowParallelEdges, int vertexCapacity)
            : base(allowParallelEdges, vertexCapacity) { }

        #endregion Constructors

        #region Public Methods

        public void AddNewVertex(CustomVertex newVertex)
        {
            AddVertexToGraph(newVertex);
        }

        public void AddNewVertexToSource(CustomVertex source, CustomVertex newVertex)
        {
            AddVertexToGraph(newVertex);
            AddNewEdgeBetween(source, newVertex);
            OnRefreshNeeded(newVertex);
        }

        public void AddNewSourceToVertex(CustomVertex target, CustomVertex newVertex)
        {
            AddVertexToGraph(newVertex);
            AddNewEdgeBetween(newVertex, target);
            OnRefreshNeeded(newVertex);
        }

        public void AddNewVertexBetween(CustomVertex source, CustomVertex target, CustomVertex newVertex)
        {
            var edge = Edges.First(e => e.Source.Equals(source) && e.Target.Equals(target));
            var edgeSequence = edge.Sequence;
            RemoveEdge(edge);
            AddVertexToGraph(newVertex);
            AddNewEdgeBetween(source, newVertex);
            AddNewEdgeBetween(newVertex, target, edgeSequence);
            OnRefreshNeeded(newVertex);
        }

        public bool AddNewEdgeBetween(CustomVertex source, CustomVertex target, int? edgeSequence = null)
        {
            //the graph must contain source and target
            //source and target must not be the same
            if (!Vertices.Contains(source)
                || !Vertices.Contains(target)
                || source == target)
            {
                return false;
            }
            bool isValid = false;
            if (!ExistsPath(target, source))
            {
                var newEdge = new CustomEdge(source, target);
                isValid = AddEdge(newEdge);
                if (isValid)
                {
                    if (edgeSequence.HasValue)
                    {
                        newEdge.Sequence = edgeSequence.Value;
                    }
                    else
                    {
                        InitSequence(newEdge);
                    }
                    RefreshHasSequence(newEdge);
                    OnRefreshNeeded(target);
                }
            }

            return isValid;
        }

        public void DeleteEdge(CustomEdge edge)
        {
            RefreshSequencesForDeletedEdge(edge);
            var dependentVertex = edge.Target;
            var otherEdges = Edges.Where(p => p.Target == dependentVertex && p.Sequence > edge.Sequence);
            if (!RemoveEdge(edge))
            {
                throw new Exception(String.Concat("Edge between ", edge.Source.Name, " and ", edge.Target.Name, " could not be deleted."));
            }
            RefreshHasSequence(edge);
            OnRefreshNeeded(dependentVertex);
        }

        public void DeleteVertex(CustomVertex vertex)
        {
            var edgesWhichWillBeDeleted = Edges.Where(p => p.Source == vertex).ToList();
            foreach (var edge in edgesWhichWillBeDeleted)
            {
                RefreshSequencesForDeletedEdge(edge);
            }
            var dependentVertices = GetChilds(vertex);
            if (!RemoveVertex(vertex))
            {
                throw new Exception(String.Concat("Vertex named ", vertex.Name, " could not be deleted."));
            }
            foreach (var dependentVertex in dependentVertices)
            {
                OnRefreshNeeded(dependentVertex);
            }
            foreach (var edge in edgesWhichWillBeDeleted)
            {
                RefreshHasSequence(edge);
            }
        }

        public bool CanDeleteVertex(CustomVertex vertex)
        {
            if (Vertices.Count() < 2)
                return true;

            afterDeleteList = new List<CustomVertex>();
            CreateVertexCollectionAfterDelete(Vertices.First(p => p != vertex), vertex);
            return afterDeleteList.Count == Vertices.Count() - 1;
        }



        public IEnumerable<CustomVertex> GetChilds(CustomVertex parent)
        {
            var edges = Edges.Where(p => p.Source == parent);
            var childs = new List<CustomVertex>();
            foreach (var edge in edges)
            {
                childs.Add((CustomVertex)edge.Target);
            }

            return childs;
        }

        public IEnumerable<CustomVertex> GetParents(CustomVertex child)
        {
            var edges = from e in Edges
                        where e.Target == child
                        orderby e.Sequence
                        select e;
            var parents = new List<CustomVertex>();
            foreach (var edge in edges)
            {
                parents.Add(edge.Source);
            }

            return parents;
        }

        public void Clear()
        {
            var vertices = Vertices.ToList();
            foreach (var customVertex in vertices)
            {
                RemoveVertex(customVertex);
            }
        }

        #endregion Public Methods

        #region Public Properties

        public bool IsGraphEmpty
        {
            get { return !Vertices.Any(); }
        }

        public bool HasParents(CustomVertex child)
        {
            return GetParentsCount(child) > 0;
        }

        public int GetParentsCount(CustomVertex child)
        {
            return GetParents(child).Count();
        }

        public bool HasSave
        {
            get { return Vertices.Any(p => p.IMOperator.Name == "Save"); }
        }

        public CustomVertex GetSave()
        {
            return Vertices.First(p => p.IMOperator.Name == "Save");
        }

        public CustomVertex GetLast()
        {
            return Vertices.First(p => !GetChilds(p).Any());
        }

        #endregion Public Properties

        #region Private Methods

        private void AddVertexToGraph(CustomVertex newVertex)
        {
            newVertex.PropertyChanged += new PropertyChangedEventHandler(VertexPropertyChanged);
            newVertex.IMOperator.PropertyChanged += new PropertyChangedEventHandler(IMOperatorPropertyChanged);
            AddVertex(newVertex);
        }

        private void IMOperatorPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName.StartsWith("Argument")
                || e.PropertyName.StartsWith("Setting"))
            {
                var imOperator = (Operator)sender;
                var vertex = Vertices.First(p => p.IMOperator == imOperator);
                OnRefreshNeeded(vertex);
            }
            if (e.PropertyName == "Argument.IsParameter")
            {
                var imOperator = (Operator)sender;
                var pathArgument = (PathArgument)imOperator.Argument;
                if (pathArgument.IsParameter)
                {
                    UnsetOtherIsParameter((Operator)sender);
                }
            }
        }

        private void UnsetOtherIsParameter(Operator newOperatorWithIsParameter)
        {
            var pathArgumentVertices = Vertices.Where(p => p.IMOperator.Argument!=null && p.IMOperator.Argument.Type == ArgumentType.path);
            var saveOperator = HasSave ? GetSave().IMOperator : null;

            if (newOperatorWithIsParameter == saveOperator)
                return;

            foreach (var vertex in pathArgumentVertices)
            {
                if (vertex.IMOperator != newOperatorWithIsParameter
                    && vertex.IMOperator != saveOperator)
                {
                    var pathArgument = (PathArgument)vertex.IMOperator.Argument;
                    pathArgument.IsParameter = false;
                }
            }
        }

        private void VertexPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "PreviewImagePath")
            {
                var vertex = (CustomVertex)sender;
                foreach (var child in GetChilds(vertex))
                {
                    OnRefreshNeeded(child);
                }
            }
        }

        private bool ExistsPath(CustomVertex from, CustomVertex to)
        {
            var childs = new List<CustomVertex>();
            childs.AddRange(GetChilds(from));
            foreach (var child in childs)
            {
                if (child == to
                    || ExistsPath(child, to))
                {
                    return true;
                }
            }

            return false;
        }

        private void InitSequence(CustomEdge edge)
        {
            var target = edge.Target;
            var allEdgesForTarget = Edges.Where(p => p.Target == target);
            edge.Sequence = allEdgesForTarget.Count();
        }

        private void RefreshSequencesForDeletedEdge(CustomEdge edge)
        {
            var otherEdges = Edges.Where(p => p.Target == edge.Target && p.Sequence > edge.Sequence);
            foreach (var otherEdge in otherEdges)
            {
                otherEdge.Sequence--;
            }
        }

        private void RefreshHasSequence(CustomEdge edge)
        {
            var allEdgesForTarget = Edges.Where(p => p.Target == edge.Target);
            foreach (var edgeForTarget in allEdgesForTarget)
            {
                edgeForTarget.HasSequence = allEdgesForTarget.Count() > 1;
            }
        }

        protected void OnRefreshNeeded(CustomVertex vertex)
        {
            if (null != refreshNeededEvent)
            {
                refreshNeededEvent(this, new RefreshNeededEventArgs(vertex));
            }
        }

        private void CreateVertexCollectionAfterDelete(CustomVertex startVertex, CustomVertex deleteVertex)
        {
            if (!afterDeleteList.Contains(startVertex)
                && startVertex != deleteVertex)
            {
                afterDeleteList.Add(startVertex);
                foreach (var parent in GetParents(startVertex))
                {
                    CreateVertexCollectionAfterDelete(parent, deleteVertex);
                }

                foreach (var child in GetChilds(startVertex))
                {
                    CreateVertexCollectionAfterDelete(child, deleteVertex);
                }
            }
        }
        #endregion Private Methods
    }
}
